<style>
    /* Custom-file focus styling: These can possibly be removed once BSV4.beta.3 is released */
    /* Although the focus handling/style will still be needed for Firefox keyboard-only users */
    /* Which will be addressed if PR https://github.com/twbs/bootstrap/pull/24138 is merged */

    /* Regular focus styling */
    .b-form-file.custom-file .custom-file-input.focus ~ .custom-file-control,
    .b-form-file.custom-file .custom-file-input:focus ~ .custom-file-control {
        color: #495057;
        background-color: #fff;
        border-color: #80bdff;
        box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
        outline: none;
    }
    /* Invalid focus styling */
    .b-form-file.custom-file .custom-file-input.is-invalid.focus ~ .custom-file-control,
    .b-form-file.custom-file .custom-file-input.is-invalid:focus ~ .custom-file-control,
    .was-validated .b-form-file.custom-file .custom-file-input:invalid.focus ~ .custom-file-control,
    .was-validated .b-form-file.custom-file .custom-file-input:invalid:focus ~ .custom-file-control {
        box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
        border-color: #dc3545;
    }
    /* valid focus styling */
    .b-form-file.custom-file .custom-file-input.is-valid.focus ~ .custom-file-control,
    .b-form-file.custom-file .custom-file-input.is-valid:focus ~ .custom-file-control,
    .was-validated .b-form-file.custom-file .custom-file-input:valid.focus ~ .custom-file-control,
    .was-validated .b-form-file.custom-file .custom-file-input:valid:focus ~ .custom-file-control {
        box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);
        border-color: #28a745;
    }
    
    /* Interim fix (until BS V4.beta.3) for is-{state} feedback for plain form-file input */
    /* See issue https://github.com/twbs/bootstrap/issues/24831 */

    .form-control-file.is-invalid ~ .invalid-feedback,
    .form-control-file.is-valid ~ .valid-feedback,
    .was-validated .form-control-file:invalid ~ .invalid-feedback,
    .was-validated .form-control-file:valid ~ .valid-feedback {
        display: block;
    }

    /* Drag/Drop and filenames/prompts custom styling */

    .b-form-file.custom-file .custom-file-control {
        overflow: hidden;
    }
    .b-form-file.custom-file .custom-file-control {
        overflow: hidden;
    }
    .b-form-file.custom-file .custom-file-control.dragging {
        overflow: hidden;
        filter: blur(3px);
    }
    .b-form-file.custom-file .custom-file-control[data-selected]::after {
        content: attr(data-selected);
    }
    .b-form-file.custom-file .custom-file-control[data-choose]::before {
        content: attr(data-choose);
    }
    .b-form-file.custom-file .drop-here {
        position: absolute;
        left: 0;
        right: 0;
        top: 0;
        bottom: 0;
        background-color: rgba(0, 0, 0, .5);
        border-radius: 3px;
        z-index: 99999;
        display: flex;
        justify-content: center;
        align-items: center;
    }
    .b-form-file.custom-file .drop-here::before {
        color: white;
        content: attr(data-drop);
    }
</style>

<script>
    import { idMixin, formStateMixin, formCustomMixin, formMixin } from '../../mixins';
    import { from as arrayFrom } from '../../utils/array';

    export default {
        mixins: [idMixin, formMixin, formStateMixin, formCustomMixin],
        render(h) {
            const t = this;

            // Form Input
            const input = h(
                'input',
                {
                    ref: 'input',
                    class: t.inputClasses,
                    attrs: {
                        type: 'file',
                        id: t.safeId(),
                        name: t.name,
                        disabled: t.disabled,
                        required: t.required,
                        capture: t.capture || null,
                        'aria-required': t.required ? 'true' : null,
                        accept: t.accept || null,
                        multiple: t.multiple,
                        webkitdirectory: t.directory,
                        'aria-describedby': t.plain ? null : t.safeId('_BV_file_control_')
                    },
                    on: {
                        change: t.onFileChange,
                        focusin: t.focusHandler,
                        focusout: t.focusHandler
                    }
                }
            );

            if (t.plain) {
                return input;
            }
            
            // 'Drop Here' target
            let droptarget = h(false);
            if (t.dragging) {
                droptarget = h(
                    'span',
                    {
                        class: [ 'drop-here' ],
                        attrs: { 'data-drop': t.dropLabel },
                        on: {
                            dragover: t.dragover,
                            drop: t.drop,
                            dragleave: t.dragleave
                        }
                    }
                );
            }

            // Overlay Labels
            const labels = h(
                'span',
                {
                    class: [ 'custom-file-control', t.dragging ? 'dragging' : null ],
                    attrs: {
                        id: t.safeId('_BV_file_control_'),
                        'data-choose': t.computedChooseLabel,
                        'data-selected': t.selectedLabel
                    }
                }
            );
            
            // Return rendered custom file input
            return h(
                'label',
                {
                    class: [ 'custom-file', 'b-form-file', t.stateClass, 'w-100', 'd-block' ],
                    attrs: { id: t.safeId('_BV_file_outer_') },
                    on: { dragover: t.dragover }
                },
                [ droptarget, input, labels ]
            );
        },
        data() {
            return {
                selectedFile: null,
                dragging: false,
                hasFocus: false
            };
        },
        props: {
            accept: {
                type: String,
                default: ''
            },
            capture: {
                // Instruct input to capture from camera
                type: Boolean,
                default: false
            },            
            placeholder: {
                type: String,
                default: null
            },
            chooseLabel: {
                type: String,
                default: null
            },
            multiple: {
                type: Boolean,
                default: false
            },
            directory: {
                type: Boolean,
                default: false
            },
            noTraverse: {
                type: Boolean,
                default: false
            },
            selectedFormat: {
                type: String,
                default: ':count Files'
            },
            noDrop: {
                type: Boolean,
                default: false
            },
            dropLabel: {
                type: String,
                default: 'Drop files here'
            }
        },
        computed: {
            inputClasses() {
                return [
                    {
                        'form-control-file': this.plain,
                        'custom-file-input': this.custom,
                        'w-100': true, // BS4 beta missing this
                        'focus': this.custom && this.hasFocus,
                    },
                    this.stateClass
                ]
            },
            selectedLabel() {
                if (!this.selectedFile || this.selectedFile.length === 0) {
                    return this.placeholder || 'No file chosen';
                }
                if (this.multiple) {
                    if (this.selectedFile.length === 1) {
                        return this.selectedFile[0].name;
                    }
                    return this.selectedFormat
                        .replace(':names', this.selectedFile.map(file => file.name).join(','))
                        .replace(':count', this.selectedFile.length);
                }
                return this.selectedFile.name;
            },
            computedChooseLabel() {
                return this.chooseLabel || (this.multiple ? 'Choose Files' : 'Choose File');
            }
        },
        watch: {
            selectedFile(newVal, oldVal) {
                if (newVal === oldVal) {
                    return;
                }
                if (!newVal && this.multiple) {
                    this.$emit('input', []);
                } else {
                    this.$emit('input', newVal);
                }
            }
        },
        methods: {
            focusHandler(evt) {
                // Boostrap v4.beta doesn't have focus styling for custom file input
                // Firefox has a borked '[type=file]:focus ~ sibling' selector issue,
                // So we add a 'focus' class to get around these "bugs"
                if (this.plain || evt.type === 'focusout') {
                    this.hasFocus = false;
                } else {
                    // Add focus styling for custom file input
                    this.hasFocus = true;
                }
            },
            reset() {
                try {
                    // Wrapped in try in case IE < 11 craps out
                    this.$refs.input.value = '';
                } catch (e) {
                }
                // IE < 11 doesn't support setting input.value to '' or null
                // So we use this little extra hack to reset the value, just in case
                // This also appears to work on modern browsers as well.
                this.$refs.input.type = '';
                this.$refs.input.type = 'file';
                this.selectedFile = this.multiple ? [] : null;
            },
            onFileChange(evt) {
                // Always emit original event
                this.$emit('change', evt);
                // Check if special `items` prop is available on event (drop mode)
                // Can be disabled by setting no-traverse
                const items = evt.dataTransfer && evt.dataTransfer.items;
                if (items && !this.noTraverse) {
                    const queue = [];
                    for (let i = 0; i < items.length; i++) {
                        const item = items[i].webkitGetAsEntry();
                        if (item) {
                            queue.push(this.traverseFileTree(item));
                        }
                    }
                    Promise.all(queue).then(filesArr => {
                        this.setFiles(arrayFrom(filesArr));
                    });
                    return;
                }
                // Normal handling
                this.setFiles(evt.target.files || evt.dataTransfer.files);
            },
            setFiles(files) {
                if (!files) {
                    this.selectedFile = null;
                    return;
                }
                if (!this.multiple) {
                    this.selectedFile = files[0];
                    return;
                }
                // Convert files to array
                const filesArray = [];
                for (let i = 0; i < files.length; i++) {
                    if (files[i].type.match(this.accept)) {
                        filesArray.push(files[i]);
                    }
                }
                this.selectedFile = filesArray;
            },
            dragover(evt) {
                evt.preventDefault();
                evt.stopPropagation();
                if (this.noDrop || !this.custom) {
                    return;
                }
                this.dragging = true;
                evt.dataTransfer.dropEffect = 'copy';
            },
            dragleave(evt) {
                evt.preventDefault();
                evt.stopPropagation();
                this.dragging = false;
            },
            drop(evt) {
                evt.preventDefault();
                evt.stopPropagation();
                if (this.noDrop) {
                    return;
                }
                this.dragging = false;
                if (evt.dataTransfer.files && evt.dataTransfer.files.length > 0) {
                    this.onFileChange(evt);
                }
            },
            traverseFileTree(item, path) {
                // Based on http://stackoverflow.com/questions/3590058
                return new Promise(resolve => {
                    path = path || '';
                    if (item.isFile) {
                        // Get file
                        item.file(file => {
                            file.$path = path; // Inject $path to file obj
                            resolve(file);
                        });
                    } else if (item.isDirectory) {
                        // Get folder contents
                        item.createReader().readEntries(entries => {
                            const queue = [];
                            for (let i = 0; i < entries.length; i++) {
                                queue.push(this.traverseFileTree(entries[i], path + item.name + '/'));
                            }
                            Promise.all(queue).then(filesArr => {
                                resolve(arrayFrom(filesArr));
                            });
                        });
                    }
                });
            }
        }
    };
</script>
